home *** CD-ROM | disk | FTP | other *** search
/ Pascal Super Library / Pascal Super Library (CW International)(1997).bin / OVERLAY / SWAPLOG / SWAPLOG.PAS
Pascal/Delphi Source File  |  1993-12-22  |  29KB  |  800 lines

  1. unit swaplog;
  2. { original unit SWAPLOG, written by Tom Field - 76247,3024 as of 30 Aug 91 }
  3. { current unit SWAPLOG, written by Mark Reichert - 72763,2417 as of 13 Dec 93 }
  4.  
  5. { This unit intercepts overlay load operations and prints a log of each
  6.   overlay load.  It is useful in studying the overlay loading in a
  7.   program when trying to eliminate thrashing.
  8.  
  9.   The unit must find a _current_ .MAP file (produced by TPC /GS) in the
  10.   executable directory. If during swapping, a segment is requested that
  11.   was not in the map file, the segment address is returned, preceded by
  12.   a question mark.
  13.  
  14.   The unit is not as self initializing as the one written by Tom Field.
  15.   You should put it in your mainline's uses list after the "overlay"
  16.   unit is used.  Actually, the saving of the BP OverReadFunc and its
  17.   replacement with the one here must be done after the OvrInit and if
  18.   necessary, the OvrInitEMS, wherever they are called.  This is
  19.   necessary because the filling of the OverReadFunc address location
  20.   with the address of the native function is done in OvrInit and
  21.   redone in OvrInitEMS.  Now, the call of the InitSwap function MUST
  22.   be done after any OvrSetBuf because OvrSetBuf needs the heap to be
  23.   EMPTY when it tries to setup the conventional memory overlay buffer.
  24.  
  25.   The following is how the setup was done when the unit was tested in
  26.   the TVDEMO program in \BP\EXAMPLES\DOS\TVDEMO.  An overlayed version
  27.   of this program was tested first, rather than the program written to
  28.   demo the use of overlays and resources, TVRDEMO, because I didn't
  29.   want the complication of resources.  By the way, use of this unit has
  30.   convinced me that, for event-driven programs at least, EMS memory or
  31.   not, the overlay buffer needs to be large enough to hold the three or
  32.   four largest and/or frequently called units or the enormous amount of
  33.   thrashing will really slow down the program
  34.  
  35. (* This procedure allows the switch to be done and redone more easily *)
  36. Procedure SaveAReadBuf;
  37. Begin
  38.   If SwapLog.GoodInitSwap Then
  39.     begin
  40.       SwapLog.SaveOvrRead := OverLay.OvrReadBuf;
  41.       OVERLAY.OvrReadBuf  := SwapLog.SwapOverRead;
  42.     end;
  43. End;
  44.  
  45. (* If an EMPTY string is fed to this procedure, and is returned still
  46.    empty, then OvrResult needs to be reexamined *)
  47. Procedure SetErrorStr(Var ErrorStr : String);
  48. Begin
  49.    Case OvrResult Of
  50.      ovrError       : ErrorStr := 'General Overlay Manager error.';
  51.      ovrNotFound    : ErrorStr := 'No OVR file not found in EXE dir.';
  52.      ovrNoMemory    : ErrorStr := 'Not enough memory for overlay buffer.';
  53.      ovrIOError     : ErrorStr := 'General Overlay file I/O Error.';
  54.      ovrNoEMSDriver : ErrorStr := 'No EMS Driver (EMM386, QEMM, etc) installed.';
  55.      ovrNoEMSMemory : ErrorStr := 'Insufficient EMS memory available';
  56.    End;
  57. End;
  58.  
  59. var
  60.   (* original program variables *)
  61.   Demo: TTVDemo;
  62.   EXEName: PathStr;
  63.   Dir: DirStr;
  64.   Name: NameStr;
  65.   Ext: ExtStr;
  66.  
  67.   UsingEMS : Boolean;
  68.   TempStr  : String;
  69.  
  70. begin
  71.   (* try to find the correct path and name for the overlay file *)
  72.   if Lo(DosVersion) >= 3 then EXEName := ParamStr(0)
  73.   else EXEName := FSearch('TVDEMOC.EXE', GetEnv('PATH'));
  74.   FSplit(EXEName, Dir, Name, Ext);
  75.   if Dir[Length(Dir)] = '\' then Dec(Dir[0]);
  76.   EXENAME := FSearch('TVDEMOC.OVR', Dir);
  77.  
  78.   (* try to initialize the overlay manager and units *)
  79.   OvrInit(EXEName);
  80.   if OvrResult <> ovrOk then
  81.   begin
  82.     TempStr := '';
  83.     SetErrorStr(TempStr);
  84.     If TempStr <> '' Then
  85.       PrintStr(TempStr+#13#10);
  86.     Halt(1);
  87.   end
  88.   Else
  89.     Begin
  90.       (* Since OvrSetBuf only affects the conventional memory overlay
  91.          buffer, it can be done before OverInitEMS *)
  92.       OvrSetBuf(48 * 1024);
  93.  
  94.       (* open the overlay log file *)
  95.       OpenOverLogFile('OVERLOG.FIL');
  96.  
  97.       (* Set when you want the procedure FlushLog to act -
  98.            NoFlush - has no effect, write to file done when buffer fills
  99.            FlushToDos - flushes OverLog file variable buffer to DOS buffers
  100.            FlushToDisk - flushes OverLog file variable buffer to disk file *)
  101.       SetTypeOfFlush(FlushToDisk);
  102.  
  103.       (* Set up the Collection Object, here with 40 items to start and
  104.          a 10 item increase whenever the limit is reached *)
  105.       GoodInitSwap := InitSwap(40, 10);
  106.  
  107.       SwapLogWrite('Did OvrInit and OvrSetBuf');
  108.       Str(OvrGetBuf:0, TempStr);
  109.       SwapLogWrite('BuffSize = ' + TempStr );
  110.  
  111.       (* Save the BP OverReadFunc and substitute our own *)
  112.       SaveAReadBuf;
  113.     End;
  114.   UsingEMS := False;
  115.   SwapLogWrite('Doing OvrInitEMS');
  116.   (* try to overlay units to EMS memory and redirect manager there
  117.      when units need to be swapped into and out of the overlay buffer *)
  118.   OvrInitEMS;
  119.   If OvrResult = OvrOk Then
  120.     UsingEMS := True
  121.   Else
  122.     Begin
  123.      (* if there is an error, just report it.  Conventional overlay
  124.         management will still go on, so don't Halt the program *)
  125.       TempStr := '';
  126.       SetErrorStr(TempStr);
  127.       If TempStr <> '' Then
  128.         SwapLogWrite(TempStr);
  129.     End;
  130.  
  131.   If UsingEMS Then
  132.     Begin
  133.       SaveAReadBuf;
  134.       SwapLogWrite('Using Expanded')
  135.     End
  136.   Else
  137.     SwapLogWrite('Using Conventional');
  138.  
  139.   Demo.Init;
  140.   Demo.Run;
  141.   Demo.Done;
  142.  
  143.   (* Write out the overlayed segments sorted by LoadCount *)
  144.   WriteSortedSegmentsToLog(OvrSegLoadCount);
  145. }
  146.  
  147. interface
  148. Uses
  149.    Dos,
  150.    Overlay;
  151.  
  152. Type
  153.   { For TSegmentItem Record }
  154.   string8      = string[8];
  155.  
  156.   { Flags for controlling how the text log file will be written }
  157.   FlushType    = (NoFlush, FlushToDos, FlushToDisk);
  158.  
  159.   { Flags for controlling what sort is done in WriteSortedSegmentsToLog }
  160.   SortType      = (OvrSegNo, OvrSegName, OvrSegLoadCount);
  161.  
  162.   { Record that will be the item controlled by TSegmentCollection Object }
  163.   { made global in hopes that will aid typecasts for debugging purposes }
  164.   PSegmentItem = ^TSegmentItem;
  165.   TSegmentItem = record
  166.     SegNo : Word;
  167.     SegName   : String8;
  168.     LoadCount : LongInt;
  169.   end;
  170.  
  171. Var
  172.   { store the BP OvrReadFunc here }
  173.   SaveOvrRead  : OVERLAY.OvrReadFunc;
  174.  
  175.   { tells the calling program that a successful it occured }
  176.   GoodInitSwap : Boolean;
  177.  
  178.   { Function to be called after a OvrSetBuf is done because OvrSetBuf needs the
  179.     heap to be empty before it runs }
  180.   Function InitSwap(ALimit, ADelta: Integer) : boolean;
  181.  
  182.   { function to replace BP's OvrReadFunc }
  183.   Function SwapOverRead( OvrSeg : Word): integer; far;
  184.  
  185.   { Procedure to allow user to write messages to the log file }
  186.   Procedure SwapLogWrite(InStr : String);
  187.  
  188. { Procedure to allow user to set when the log disk file is actually written to }
  189.   Procedure SetTypeOfFlush(InFlushType : FlushType);
  190.  
  191. { Seperating Log File Opening out of InitSwap allows a SwapLogWrite before OvrSetBuf }
  192.   Procedure OpenOverLogFile(InName : PathStr);
  193.  
  194. { Procedure to allow Writing Sorted List of Segments and Counts at any point of
  195.   program;  Order is reset to SegNo at end of this procedure so that later lookups
  196.   will work. }
  197. Procedure WriteSortedSegmentsToLog(SortChoice : SortType);
  198.  
  199. implementation
  200.  
  201. uses
  202.      Objects,     { To inherit from TSortedCollection Object }
  203.      IOChek;      { has functions with internal I/O Checking, also in Library }
  204.                   { This unit is in Dos Programming in the BP CompuServe Library }
  205. type
  206.   string4       = string[4];
  207.   string19      = String[19];
  208.  
  209.   TSortFunc = function(P1, P2: PSegmentItem): Integer;
  210.  
  211.   PSegmentCollection = ^TSegmentCollection;
  212.   TSegmentCollection = object(TSortedCollection)
  213.     Procedure SetLimit(ALimit: Integer); virtual;
  214.     Function Compare(Key1, Key2: Pointer): Integer; virtual;
  215.     Procedure FreeItem(Item : Pointer); virtual;
  216.     Procedure ReOrder;
  217.   end;
  218.  
  219. Function SortBySegNo(P1, P2: PSegmentItem): Integer; far; assembler;
  220. asm
  221.   les di, P1      { load first pointer }
  222.   mov ax, es:[di] { Put word value at ES:DI (SegNo) into AX }
  223.   les di, P2      { load second pointer }
  224.   sub ax, es:[di] { compare SegNo values }
  225.   jz @end         { 0 is the return value for P1^.SegNo = P2^.SegNo }
  226.   rcr al, 1       { rotate CF=>sign bit for AL, CF=1 =>Neg AL, CF=0 =>Pos AL }
  227.   or al, 1        { make sure that AL <> 0 }
  228.   cbw             { Convert Byte to Word => make signed AX = signed AL }
  229. @end:
  230. End;
  231.  
  232. { Most of the code here was borrowed from the StrCollection Compare
  233.   in the Objects unit }
  234. Function SortBySegName(P1, P2: PSegmentItem): Integer; far; assembler;
  235. asm
  236.    PUSH    DS
  237.    CLD                   { string operations in forward mode }
  238.    LDS     SI,P1
  239.    ADD     SI,OFFSET TSEGMENTITEM.SEGNAME  { point DS:SI to P1^.SegName }
  240.    LES     DI,P2
  241.    ADD     DI,OFFSET TSEGMENTITEM.SEGNAME  { point ES:DI to P2^.SegName }
  242.    LODSB               { put P1^.SegName length byte in AL and inc SI past it }
  243.    MOV     AH,ES:[DI]
  244.    INC     DI          { put P2^.SegName length byte in AH and inc DI past it }
  245.    MOV     CL,AL       { this and the next 3 lines do the following }
  246.    CMP     CL,AH
  247.    JBE     @@1         { CL = Min(Length(P1^.SegName), Length(P2^.SegName) }
  248.    MOV     CL,AH
  249. @@1:    XOR     CH,CH  { make CX = CL }
  250.    REP     CMPSB       { compare until unequal chars found or end of shorter }
  251.    JE      @@2          { if one is substring of other, compare lengths }
  252.    MOV     AL,DS:[SI-1] { otherwise REP inc'd past unequal chars so put }
  253.    MOV     AH,ES:[DI-1] { them in AL and AH, so that subtraction will make }
  254. @@2:    SUB     AL,AH   { AX < 0 if P1^.SegName < P2^.SegName }
  255.    SBB     AH,AH        { and AX > 0 if P1^.SegName > P2^.SegName }
  256.    POP     DS
  257. end;
  258.  
  259. Function SortByLoadCount(P1, P2: PSegmentItem): Integer; far; assembler;
  260. asm
  261.   push ds
  262.   lds si, P1      { load first pointer }
  263.   add si, offset TSEGMENTITEM.LOADCOUNT { point DS:SI to P1^.LOADCOUNT }
  264.   les di, P2      { load second pointer }
  265.   add di, offset TSEGMENTITEM.LOADCOUNT { point ES:DI to P2^.LOADCOUNT }
  266.   mov ax, [si+2]    { Put high word value at DS:SI into AX }
  267.   sub ax, es:[di+2] { compare high word values of P1^ and P2^ LoadCount }
  268.   jnz @end          { If high words not equal, AX properly <0 or >0 }
  269.                     { 0 < Hi word < MaxInt, so no RCR needed as it is below }
  270.   mov ax, [si]      { Put low word value at DS:SI into AX }
  271.   sub ax, es:[di]   { compare low word values of P1^ and P2^ LoadCount }
  272.   jz @end         { 0 is the return value for P1^.LoadCount = P2^.LoadCount }
  273.   rcr al, 1       { rotate CF=>sign bit for AL, CF=1 =>Neg AL, CF=0 =>Pos AL }
  274.   or al, 1        { make sure that AL <> 0 }
  275.   cbw             { Convert Byte to Word => make signed AX = signed AL }
  276. @end:
  277.   pop ds
  278. End;
  279.  
  280. var
  281.   { When the object is relatively small and will stay within the unit, no need
  282.     to add another layer of redirection by using the Pointer to the object }
  283.   SegmentDB: TSegmentCollection;
  284.  
  285.   { holds the sort requested by the WriteSortedSegmentsToLog Procedure }
  286.   SortUsed : SortType;
  287.  
  288. const Sorts : array[SortType] of TSortFunc =
  289.                            (SortBySegNo, SortBySegName, SortByLoadCount);
  290.       SortsStr : array[SortType] of String19 =
  291.                            ('Segment Number', 'Segment Name', 'Segment Load Count');
  292.  
  293. procedure TSegmentCollection.SetLimit(ALimit: Integer);
  294. begin
  295.   inherited SetLimit(ALimit);
  296.   { NIL all pointers after the active ones - with a zero-indexed array,
  297.     the COUNTth item is the one after the last active element }
  298.   { good for debugging and using Assigned to avoid using invalid pointers }
  299.   { If Starting and Count = 0, then the whole array is initialized }
  300.    If Limit > Count Then
  301.      FillChar(Items^[Count], (Limit - Count) * SizeOf(Pointer), 0);
  302. end;
  303.  
  304. { Build of Collection and Lookups are done by Segment Number }
  305. function TSegmentCollection.Compare(Key1, Key2: Pointer): Integer;
  306. begin
  307.   Compare := SortBySegNo(Key1, Key2);
  308. end;
  309.  
  310. { Due to the FillChar in Descendant SetLimit, the Assigned should prevent
  311.   the Disposing of any Invalid pointers }
  312. procedure TSegmentCollection.FreeItem(Item : Pointer);
  313. begin
  314.   If Assigned(Item) Then
  315.     Dispose(PSegmentItem(Item));
  316. end;
  317.  
  318. { In the example program off of which I patterned this sort, Compare was used
  319.   directly, but that overburdened it so that the Lookups would have taken much
  320.   longer, maybe slowing the program down }
  321.  
  322. Function SortCompare(Key1, Key2: Pointer): Integer;
  323. var Result   : Integer;
  324.     SortIndx : SortType;
  325. Begin
  326.   { at the top of the array Key2 would be nil }
  327.   if Key2 = nil then
  328.     begin
  329.       SortCompare := 0;
  330.       Exit;
  331.     end;
  332.   { Do the Selected Sort }
  333.   Result := Sorts[SortUsed](Key1, Key2);
  334.  
  335.   { if the sort is by LoadCount then it should be descending to
  336.     ease the sighting of the most frequently used units,
  337.     so reverse the Result variable to make a descending sort }
  338.   if SortUsed = OvrSegLoadCount Then
  339.     If Result <> 0 then
  340.       Result := Result * -1
  341.     Else
  342.       { units CANNOT have the same name or segment mapping number so the
  343.         Result will NOT be 0;  LoadCounts can be the same so get
  344.         alphabetical name order in that case }
  345.       Result := Sorts[OvrSegName](Key1, Key2);
  346.  
  347.   SortCompare := Result;
  348. End;
  349.  
  350. procedure TSegmentCollection.ReOrder;
  351.  
  352.   { This does a Quicksort, which divides the items into those lesser and
  353.     greater to "x", and then uses recursion to do the same with to each
  354.     subsequently smaller divided area until reaching indivisible single items}
  355.   procedure Sort(l, r: Integer);
  356.   var
  357.     i, j: Integer;
  358.     x, p: Pointer;
  359.   begin
  360.     repeat
  361.       i := l; j := r;
  362.       x := KeyOf(Items^[(l + r) div 2]);
  363.       repeat
  364.         while SortCompare(KeyOf(Items^[i]), x) < 0 do Inc(i);
  365.         while SortCompare(x, KeyOf(Items^[j])) < 0 do Dec(j);
  366.         if i <= j then
  367.         begin
  368.             if i < j then
  369.               begin
  370.                 p := Items^[i];
  371.                 Items^[i] := Items^[j];
  372.                 Items^[j] := p;
  373.               end;
  374.             Inc(i); Dec(j);
  375.         end;
  376.       until i > j;
  377.       if l < j then Sort(l, j);
  378.       l := i;
  379.     until l >= r;
  380.   end;
  381.  
  382. begin
  383.   if Count > 1 then Sort(0, Count - 1);
  384. end;
  385.  
  386. Procedure WriteSortedSegmentsToLog(SortChoice : SortType);
  387. Var I : Integer;
  388.     P : PSegmentItem;
  389.     LCStr : String8;
  390. Begin
  391.   { ReOrder uses this Unit Variable SortUsed }
  392.   SortUsed := SortChoice;
  393.   { The normal order is by SegNo }
  394.   If SortUsed <> OvrSegNo Then
  395.     SegmentDB.Reorder;
  396.   SwapLogWrite('');
  397.   SwapLogWrite('Overlay Segments And LoadCounts Sorted With Primary Key = ' +
  398.                         SortsStr[SortUsed]);
  399.   { the Items Array accessed by At is zero based, from 0 to Count - 1 }
  400.   For I := 0 to Pred(SegmentDB.Count) do
  401.     Begin
  402.       { Get the Ith PSegmentItem Pointer }
  403.       P := SegmentDB.At(I);
  404.       { We only want to list the units that are overlayed;
  405.         The initialization of the Collection does a Lookup immediately after
  406.         inserting a PSegmentItem in to make sure it was a valid Insert,
  407.         which makes LoadCount = 1 before the actual work begins }
  408.       With P^ do
  409.         Begin
  410.           If LoadCount > 1 Then
  411.             Begin
  412.               Str(LoadCount:0, LCStr);
  413.               SwapLogWrite(SegName + ' : ' + LCStr);
  414.             End;
  415.         End;
  416.     End;
  417.   If SortUsed <> OvrSegNo Then
  418.     Begin
  419.       { Reorder by SegNo so that further overlay logging can be done }
  420.       SortUsed := OvrSegNo;
  421.       SegmentDB.Reorder;
  422.     End;
  423. End;
  424.  
  425. function NameSegment(Const SegRec : TSegmentItem) : Boolean;
  426. var
  427.   P: PSegmentItem;
  428. begin
  429.   NameSegment := False;
  430.   New(P);
  431.   If Assigned(P) Then
  432.     Begin
  433.       NameSegment := True;
  434.       P^ := SegRec;
  435.       SegmentDB.Insert(P);
  436.     End;
  437. end;
  438.  
  439. Type
  440.   FlushLogFunc = Function(Var TextFile : Text) : Integer;
  441.  
  442. Var
  443.   OpenedLogFile    : Boolean;
  444.   OverLogName      : PathStr;
  445.   OverLog          : text; { text file, not printer }
  446.   OldExitProc      : Pointer;
  447.   OverLogFlushFunc : FlushLogFunc;
  448.   EXEname          : NameStr;
  449.   EXEDir           : DirStr;
  450.  
  451. Function FlushLog : Integer;
  452. Begin
  453.   FlushLog := 0;
  454.   { If no forced flushes are to be done, OverLogFlushFunc = Nil }
  455.   If Assigned(OverLogFlushFunc) Then
  456.     FlushLog := OverLogFlushFunc(OverLog);
  457. End;
  458.  
  459.  
  460. { This 58 byte function for getting string with current system date, is
  461.   only incrementally faster than an equivalent Pascal Function but it
  462.   is much smaller }
  463. Function Date : Strg12;  assembler;
  464. asm
  465.    cld
  466.    les di, @Result    { get address of output string }
  467.    mov ah, 2Ah
  468.    int 21h            { get system time thru DOS function }
  469.  
  470.    mov ax, cx         { get YEAR result in CX }
  471.  
  472.    mov bx, (100 shl 8) + '/'     { set BH = 100, BL = '/' }
  473.    div bh             { divide AX by 100, get quotient and remainder }
  474.  
  475.    mov bh, al         { save quotient (century) in BL }
  476.    mov al, 0          { set AL to no seperator, remainder already in AH }
  477.    push ax
  478.    push bx            { BX already set }
  479.    mov bh, dl         { get DAY result in DL }
  480.    push bx
  481.    mov dl, 10         { put length byte = 10 in DL, MONTH already in DH }
  482.    push dx
  483.  
  484.    mov si, 3030h      { set up SI for ADDs }
  485.    mov bl, 10         { set up BL for DIVs and MODs }
  486.    mov cx, 4          { four trips thru loop }
  487. @TopOfLoop:
  488.    pop ax             { pop something to work on off the stack }
  489.    xor dx, dx         { setup to make AX = AL, DX = AH }
  490.    xchg ah, dl        { makes DX = AH = days, months, years, or century }
  491.    cmp al, 0          { there will be no seperator between yrs and century }
  492.    jz @nosep
  493.    stosb              { store length byte or seperator }
  494. @nosep :
  495.    xchg ax, dx        { get days, months, years, or century }
  496.    div bl             { divide AX by 10, get quotient and remainder }
  497.    add ax, si         { add 3030h to quotient, remainder into char equivalent }
  498.    stosw              { store quotient and remainder in output }
  499.    loop @TopOfLoop
  500. end;
  501.  
  502. { This 49 byte function for getting string with current system time, is
  503.   only incrementally faster than an equivalent Pascal Function but it
  504.   is much smaller }
  505. Function Time : Strg12;  assembler;
  506. asm
  507.    cld
  508.    mov ah, 2Ch
  509.    int 21h            { get system time thru DOS function }
  510.    les di, @Result    { get address of output string }
  511.  
  512.    mov al, '.'        { set AL to '.' seperator }
  513.    mov ah, dl         { get HUNDREDTHS of SECOND result in DL }
  514.    push ax
  515.    mov dl, ':'        { set DL to ':' seperator, SECOND result in DH }
  516.    push dx
  517.    mov dh, cl         { get MINUTE result in CL }
  518.    push dx
  519.    mov cl, 11         { put fixed length byte of 11 in CL, HOUR is in CH }
  520.    push cx
  521.    mov si, 3030h      { set up SI for ADD }
  522.    mov bl, 10         { set up BL to make DIV do a decimal partitioning }
  523.    mov cx, 4          { four trips thru loop }
  524. @TopOfLoop:
  525.    pop ax             { pop something to work on off the stack }
  526.    xor dx, dx         { setup to make AX = AL, DX = AH }
  527.    xchg ah, dl        { makes DX = AH = 100ths, secs, mins or hours }
  528.    stosb              { store length byte or seperator }
  529.    xchg ax, dx        { get hundredths, seconds, minutes or hours }
  530.    div bl             { divide AX by 10, get quotient and remainder }
  531.    add ax, si         { add 3030h to quotient, remainder into char equivalent }
  532.    stosw              { store quotient and remainder in output }
  533.    loop @TopOfLoop
  534. end;
  535.  
  536. function ByteToHex(BB : byte) : string ; assembler ;
  537. asm
  538.   les di, @Result     { get address of output string }
  539.   mov al, 2
  540.   cld
  541.   stosb               { this string will always be 2 chars long }
  542.   mov al, BB          { get number }
  543.   mov dl, al          { save it in DL for later use }
  544.   shr al, 1
  545.   shr al, 1
  546.   shr al, 1
  547.   shr al, 1           { divide AL by 16 to get value of high char }
  548.   add al, 55          { translate to ord of equivalent char }
  549.   cmp al, 64
  550.   ja @1               { if AL was 10 to 15, skip additional step }
  551.   sub al, 7           { if AL was  0 to  9, must sub 7 to get '0' to '9' }
  552.  @1:
  553.   stosb               { store in first char spot }
  554.   mov al, dl          { restore AL to original value }
  555.   and al, 15          { wipe out high char }
  556.   add al, 55          { translate to ord of equivalent char }
  557.   cmp al, 64
  558.   ja @2               { if AL was 10 to 15, skip additional step }
  559.   sub al, 7           { if AL was  0 to  9, must sub 7 to get '0' to '9' }
  560.  @2:
  561.   stosb               { store in second char spot }
  562. end ; { ByteToHex }
  563.  
  564. Procedure OverExitProc; far;
  565. Begin
  566.   ExitProc := OldExitProc;
  567.   { Since after initialization, the Log File can be written to at any overlay
  568.     swap, we must keep the file open, and force it to be closed only on exit }
  569.   If OpenedLogFile Then
  570.     Begin
  571.       writeln(OverLog, 'Closed ' + OverLogName);
  572.       IO_CloseText(OverLog);
  573.     End;
  574. End;
  575.  
  576. { Returns the name of the segment at SegRec.SegNo in SegRec.SegName, or false }
  577. Function LookUp(Var SegRec : TSegmentItem) : boolean;
  578. var PSegItem : PSegmentItem;
  579.     I : Integer;
  580. begin
  581.   Lookup := False;
  582.   { Search in Items Array for Item with SegRec.SegNo, Return I, the index }
  583.   if SegmentDB.Search(@SegRec, I) then
  584.     Begin
  585.       { Get the Pointer to the Ith item in Items }
  586.       PSegItem := SegmentDB.At(I);
  587.       { Increment LoadCount to track how many times this unit is loaded }
  588.       Inc(PSegItem^.LoadCount);
  589.       { Return the info in SegRec to be printed }
  590.       SegRec := PSegItem^;
  591.       Lookup := True;
  592.     End
  593.   else
  594.   begin
  595.     { If the Search was unsuccessful, return the Segment Number as the name }
  596.     With SegRec do
  597.       Begin
  598.         SegName := '?' + ByteToHex(Hi(SegNo)) + ByteToHex(Lo(SegNo));
  599.         LoadCount := 0;
  600.       End;
  601.   end;
  602. end; { LookUp }
  603.  
  604. Procedure SwapLogWrite(InStr : String);
  605. Begin
  606.   { If the Write was Successful, attempt a Flush from the Overlog Buffer }
  607.   If IO_WritelnTextStr(OverLog, InStr) = 0 Then
  608.     FlushLog;
  609. End;
  610.  
  611. Function InitSwap(ALimit, ADelta: Integer) : boolean;
  612. { reads the program's map into a StringDict }
  613. var
  614.   hex_addr      : string4;    { eg 4C97     }
  615.   SegRec        : TSegmentItem;   { eg 0, OPSTRING, 0 }
  616.   InSeg, SegLine,
  617.   Stop, NotEmpty : Boolean;
  618.   ErrCode       : Integer;
  619.   mem           : longint;
  620.   map_file      : text;      { progname.map }
  621.   fname         : Dos.PathStr;  { filename }
  622.   fext          : Dos.ExtStr;
  623.   map_file_line : string;
  624. begin
  625.   InitSwap := False;
  626.   { This procedure will report the heap memory taken by the Collection }
  627.   mem := memavail;
  628.   { If the Log File is not open, we have no place to report to so stop }
  629.   If Not OpenedLogFile Then
  630.     Begin
  631.       Writeln('Could not open log file ' + OverLogName + '.');
  632.       Writeln('No logging will be done.');
  633.       Exit;
  634.     End;
  635.   { report when this log was done }
  636.   SwapLogWrite('Opened ' + OverLogName + ' on ' + Date + ' at ' + Time);
  637.  
  638.   { do the actual init of the object which if unsuccessful leaves us no
  639.     way of accomplishing our task }
  640.   If Not segmentDB.Init(ALimit, ADelta) then
  641.     Begin
  642.       SwapLogWrite('Unable to init segment mapping object');
  643.       Exit;
  644.     End;
  645.  
  646.   { EXEDir and EXEName are set in the LogFile Open; If we can't open the
  647.     map, we have no way of associating Segment numbers to unit names }
  648.   fname := EXEDir + EXEName + '.MAP';
  649.   ErrCode := IO_OpenText(fname, map_file, resetfile);
  650.   if ErrCode <> 0 then
  651.     Begin
  652.       SwapLogWrite('Unable to open map file: ' + fname);
  653.       Exit;
  654.     End;
  655.  
  656.   SwapLogWrite('Loading: ' + fname);
  657.   InSeg := False;
  658.   Stop := False;
  659.   SegLine := False;
  660.   NotEmpty := False;
  661.   while (not eof(map_file)) and (ErrCode = 0) and (Not Stop) do
  662.     begin
  663.       ErrCode := IO_ReadlnTextStr(map_file, map_file_line);
  664.       If ErrCode = 0 then
  665.         Begin
  666.           { Is the line a Valid Segment Map area line? }
  667.           SegLine := (length(map_file_line) >= 40) and (map_file_line[7] = 'H');
  668.           { Is code, or just types and constants, from the unit used? }
  669.           NotEmpty := copy(map_file_line,16,5) <> '00000';
  670.           { Until we hit a SegLine, we are not in the SegArea }
  671.           If Not InSeg Then
  672.             Begin
  673.               If SegLine Then
  674.                 InSeg := True;
  675.             End;
  676.  
  677.           If InSeg Then
  678.             If SegLine Then
  679.               Begin
  680.                 if NotEmpty Then
  681.                   begin
  682.                     { get the Hex Address String of the Unit }
  683.                     hex_addr := copy(map_file_line, 2,  4); { eg '4C97'     }
  684.                     With SegRec do
  685.                       Begin
  686.                         { Hex numbers need to be flagged by use of the '$' }
  687.                         Val('$' + Hex_Addr, SegNo, ErrCode);
  688.                         { get the unit name }
  689.                         SegName := copy(map_file_line, 23, 8); { eg 'OPSTRING' }
  690.                         { Setting up a string for latter use }
  691.                         fname := 'Lookup tested Okay for ' + SegName + ': LC = ';
  692.                         LoadCount := 0;
  693.                         SwapLogWrite('Adding ' + hex_addr +  ' ' + SegName);
  694.                       End;
  695.  
  696.                     { put the information in SegRec into the Collection }
  697.                     If Not NameSegment(SegRec) then
  698.                       Begin
  699.                         SwapLogWrite('Failed in Add when adding ' + SegRec.SegName);
  700.                         IO_CloseText(map_file);
  701.                         Exit;
  702.                       End
  703.                     Else
  704.                       { If NameSegment successful, do a lookup to make sure it
  705.                         was completely successful }
  706.                       If LookUp(SegRec) then
  707.                         begin
  708.                           Str(SegRec.LoadCount:0, EXEname);
  709.                           SwapLogWrite(fname + EXEName);
  710.                         End
  711.                       Else
  712.                         SwapLogWrite('Lookup did not test Okay for ' + SegRec.SegName);
  713.                   end;
  714.               End
  715.             Else
  716.               { allowing blank lines to get in but anything else will stop the read }
  717.               If map_file_line <> '' Then
  718.                 Stop := True;
  719.         End;
  720.   End;
  721.  
  722.   { This will show how much heap is being used by the Collection }
  723.   Str(mem - memavail:0, EXEname);
  724.   SwapLogWrite('Memory used by load= ' + EXEName);
  725.  
  726.   If ErrCode = 0 Then
  727.     ErrCode := IO_CloseText(map_file);
  728.   If ErrCode = 0 Then
  729.     InitSwap := True;
  730. end; { LoadList }
  731.  
  732. { The address of this replaces that of the native BP function, so that
  733.   the lookup and write to the log can take place before SaveOvrRead calls
  734.   the native function to do that actual overlay swap }
  735. Function SwapOverRead( OvrSeg : Word): integer;
  736. var
  737.   tempseg  : word;
  738.   hex_seg  : string4;
  739.   CountStr : String8;
  740.   SegRec   : TSegmentItem;
  741. begin
  742. (* In a program, the PrefixSeg variable contains the selector
  743.    (segment address) of the Program Segment Prefix (PSP)
  744.    created by DOS and Windows when the application was
  745.    executed. *)
  746.   SegRec.SegNo := OvrSeg - PrefixSeg - $10;
  747.   { If Lookup successful, write the unit SegName and the LoadCount }
  748.   if LookUp(SegRec) then
  749.     begin
  750.       With SegRec do
  751.         Begin
  752.           Str(LoadCount:0, CountStr);
  753.           SwapLogWrite(SegName + ' : ' + CountStr);
  754.         end;
  755.     End
  756.   Else
  757.     { If Lookup unsuccessful, write SegName which now contains the
  758.       Address as a HexStr }
  759.     SwapLogWrite(SegRec.SegName);
  760.   { Call SaveOvrRead to do the overlay swap }
  761.   SwapOverRead := SaveOvrRead(OvrSeg);
  762. end; { MyOverRead }
  763.  
  764. Procedure SetTypeOfFlush(InFlushType : FlushType);
  765. Begin
  766.   { If InFlushType = NoFlush, OverLogFlushFunc = Nil }
  767.   OverLogFlushFunc := Nil;
  768.   Case InFlushType Of
  769.     FlushToDos  : OverLogFlushFunc := IO_FlushToDos;
  770.     FlushToDisk : OverLogFlushFunc := IO_FlushToDisk;
  771.   End;
  772. End;
  773.  
  774. Procedure OpenOverLogFile(InName : PathStr);
  775. Var FEXT : EXTStr;
  776.     FDir : DirStr;
  777. Begin
  778.   { Parse to get the log file directory and name }
  779.   fsplit(InName, FDir, EXEName, FEXT);
  780.   { If no name given, default to OVERLOG.FIL }
  781.   If EXEName = '' Then
  782.     InName := 'OVERLOG.FIL';
  783.   { Parse to get the executable directory and log name }
  784.   fsplit(ParamStr(0), EXEDir, EXEName, FEXT);
  785.   { If no log directory given, default to executable directory }
  786.   If FDir = '' Then
  787.     FDir := EXEDir;
  788.   { Set the unit variable to allow writing the file name to the file }
  789.   OverLogName := FDir + InName;
  790.   { open the file and set the boolean flag accordingly }
  791.   OpenedLogFile := IO_OpenText(OverLogName, OverLog, RewriteFile) = 0;
  792. End;
  793.  
  794. begin
  795.   OldExitProc := ExitProc;
  796.   ExitProc := @OverExitProc;
  797.   GoodInitSwap := False;
  798.   OverLogFlushFunc := IO_FlushToDisk;
  799. end.
  800.